/**
 * jaroverlay-maven-plugin : add remove replace content and repackage nested archives
 * 
 * Copyright (C) 2008  Paul Merlin
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.n0pe.jaroverlay.io;


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.zip.CRC32;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;


/**
 * @author Paul Merlin <eskatos@n0pe.org>
 */
public final class ZipUtil {


    private ZipUtil() {
    }


    /**
     * Unzip to a given directory.
     * 
     * @param sourceZipFile         Archive to unzip.
     * @param targetDirectory       Target directory.
     * 
     * @throws java.io.IOException
     */
    public static void unzip(final File sourceZipFile, final File targetDirectory)
            throws IOException {
        final ZipInputStream zis = new ZipInputStream(new BufferedInputStream(
                                                      new FileInputStream(sourceZipFile.getCanonicalFile())));
        ZipEntry ze;
        try {
            // Parcourt tous les fichiers
            while (null != (ze = zis.getNextEntry())) {
                final File f = new File(targetDirectory.getCanonicalPath(), ze.getName());
                if (f.exists()) {
                    f.delete();
                }
                // Création des dossiers
                if (ze.isDirectory()) {
                    f.mkdirs();
                    continue;
                }
                f.getParentFile().mkdirs();
                final OutputStream fos = new BufferedOutputStream(new FileOutputStream(f));
                // Ecriture des fichiers
                try {
                    try {
                        final byte[] buf = new byte[4096];
                        int bytesRead;
                        while (-1 != (bytesRead = zis.read(buf))) {
                            fos.write(buf, 0, bytesRead);
                        }
                    } finally {
                        fos.close();
                    }
                } catch (final IOException ioe) {
                    f.delete();
                    throw ioe;
                }
            }
        } finally {
            zis.close();
        }
    }


    /**
     * Recursively zip a directory.
     * 
     * @param inputDir      Directory to add to the archive.
     * @param outputFile    Output archive.
     * 
     * @throws java.io.IOException
     */
    public static void recursiveZip(final File inputDir, final File outputFile)
            throws IOException {
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputFile));
        recursiveZipDir(inputDir, zos, new File(inputDir.getAbsolutePath()));
        zos.close();
    }


    private static void recursiveZipDir(final File inputDir, final ZipOutputStream zos, final File rootFile)
            throws IOException {
        BufferedInputStream bis;
        zos.setLevel(Deflater.DEFAULT_COMPRESSION);
        for (int i = 0; i < inputDir.listFiles().length; i++) {
            File curFile = inputDir.listFiles()[i];
            if (curFile.isDirectory()) {
                recursiveZipDir(curFile, zos, rootFile);
                continue;
            }
            bis = new BufferedInputStream(new FileInputStream(curFile));
            addFile(bis,
                    curFile.getPath().substring(rootFile.getAbsolutePath().length() + 1),
                    zos,
                    Deflater.DEFAULT_COMPRESSION);
            bis.close();
        }
    }


    private static void addFile(InputStream fis1, String path, ZipOutputStream zout, int compressionLevel)
            throws IOException {
        // make sure that only one file is added at a time.
        byte[] readBuffer = new byte[4000];
        // the size of the file.
        long size = 0;
        // for buffer operations.
        int len;
        CRC32 crc = new CRC32();
        // the zip entry; contains CRC, name and size information.
        ZipEntry entry;
        OutputStreamCounter counter = new OutputStreamCounter();
        Deflater deflater = new Deflater(compressionLevel, true);
        DeflaterOutputStream dout = new DeflaterOutputStream(counter, deflater);
        File tempFile = File.createTempFile("ZipUtilTempFile", "zip");
        FileOutputStream fout = new FileOutputStream(tempFile);
        crc.reset();
        while ((len = fis1.read(readBuffer)) >= 0) {
            size += len;
            crc.update(readBuffer, 0, len);
            dout.write(readBuffer, 0, len);
            fout.write(readBuffer, 0, len);
        }
        fis1.close();
        dout.close();
        fout.close();
        // create a new Zip entry.
        entry = new ZipEntry(path);
        entry.setSize(size);
        entry.setCompressedSize(counter.getWritten());
        entry.setCrc(crc.getValue());
        // re-read the file and write out the contents
        FileInputStream fis2 = new FileInputStream(tempFile);
        zout.putNextEntry(entry);
        int size2 = 0;
        while ((len = fis2.read(readBuffer)) >= 0) {
            size2 += len;
            zout.write(readBuffer, 0, len);
        }
        fis2.close();
        zout.closeEntry();
        tempFile.delete();
        if (size != size2) {
            throw new RuntimeException("Assertion failure; size: " + size + " size2: " + size2);
        }
    }


}
